package org.xtext.runtime.plan;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.xtext.runtime.cartago.ICartagoPlanStep;
import org.xtext.runtime.formula.Predicate;
import org.xtext.runtime.interpreter.Agent;
import org.xtext.runtime.term.ITerm;
import org.xtext.runtime.term.Term;
import org.xtext.runtime.term.Variable;
import org.xtext.runtime.type.TypeException;
import org.xtext.runtime.util.Bindings;
import org.xtext.runtime.util.EvaluateTermsVisitor;
import cartago.ArtifactId;
import cartago.Op;
import cartago.OpFeedbackParam;

/**
 * Implements the plan step for invoking an EIS action. 
 * @author rem
 *
 */
public class CartagoPerformStep implements IPlanStep,ICartagoPlanStep {
	private ITerm artifactId;
	private Predicate action;
	private Predicate activity;
	private int state;
	private Op op;
	
	public CartagoPerformStep(Predicate action) {
		this(null, action);
	}
	
	public CartagoPerformStep(ITerm artifactId, Predicate action) {
		this.artifactId = artifactId;
		this.action = action;
		state = 0;
	}
	
	@Override
	public CartagoPerformStep apply(Bindings set) {
		if (artifactId != null) artifactId = artifactId.apply(set);
		action = action.apply(set);
		return this;
	}

	@Override
	public void handle(Agent agent, PlanStack planStack) {
		switch (state) {
		case 0:
			EvaluateTermsVisitor visitor = new EvaluateTermsVisitor(agent);
			activity = (Predicate) action.accept(visitor);

			// ATTEMPT CARTAGO ACTION...
			LinkedList<Object> list = getArguments();
			
			Op operation = null;
			if (list.isEmpty()) {
				operation = new Op(action.identifier());
			} else {
				operation = new Op(action.identifier(), list.toArray());
			}
			
//			System.out.println("[" + agent.name() + "] About to perform operation: " + operation);
			
			try {
				long actId = -1;
				if (artifactId != null) {
					ArtifactId artId = (ArtifactId) ((Term<?>) artifactId.accept(visitor)).value();
					actId = agent.getSession().doAction(artId, operation, null, -1);
//				if ((ctxt = params.get("workspace_id")) != null) {
//					System.out.println("wspid: " + CoreUtilities.toString(ctxt));
//					WorkspaceId wspId = (WorkspaceId) ((ObjectTerm) ctxt).getObject();
//					actId = agent.getSession().doAction(wspId, operation, null, -1);
				} else {
					actId = agent.getSession().doAction(operation, null, -1);
				}
				agent.registerOperation(actId, planStack);

				planStack.back();
				planStack.suspend();
				state++;
			} catch (Throwable e) {
				agent.logAction(toString("") + " [NO SUCH ACTION]");
				planStack.fail();
				e.printStackTrace();
			}
			break;
		case 1:
			// Create bindings for any unbound variables
			Object paramValues[] = op.getParamValues();

			for (int i=0; i<activity.size(); i++) {
				ITerm term = activity.termAt(i);
				if (term instanceof Variable) {
					Variable var = (Variable) term;
					if (paramValues[i] instanceof OpFeedbackParam<?>){
						OpFeedbackParam<?> feedbackParam = (OpFeedbackParam<?>) paramValues[i];
						try {
							var.assign(Term.newInstance(feedbackParam.get()));
						} catch (TypeException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					} else {
						System.err.println("[" + agent.name() + "] Unexpected variable in cartago operation call: " + term);
						System.exit(0);
					}
				}
			}
			
//			System.out.println("[" + agent.name() + "] Completing action: " + activity);
//			planStack.printTrace();
			
			agent.logAction(toString("") + " [SUCCESS]");
			state = 0;
		}
	}

	private LinkedList<Object> getArguments() {
		LinkedList<Object> list = new LinkedList<Object>();
		for (int i=0; i<activity.size(); i++) {
			if (activity.termAt(i) instanceof Variable) {
				list.add(new OpFeedbackParam());
			} else {
				Object value = Variable.rawValue(activity.termAt(i)).value();
				if (value instanceof List) {
					list.add(((List) value).toArray());
				} else {
					list.add(value);
				}
			}
		}
		return list;
	}

	@Override
	public String toString(String tabs) {
		return tabs + "cartago_perform(" + action + ")";
	}
	
	public String toFullString(String tabs) {
		return toString(tabs);
	}
	
	@Override
	public Set<Variable> variables() {
		return new HashSet<Variable>();
	}

	@Override
	public IPlanStep copy(Map<String, Variable> vars) {
		return new CartagoPerformStep(action.copy(vars));
	}

	@Override
	public void setOperation(Op op) {
		this.op = op;
	}
	
	public String toString() {
		return toString("");
	}

	@Override
	public void reset() {
		state = 0;
	}
}
